import {
  IFetchInstance,
  IFetchInstanceWrapper,
  IFetchInterceptor,
  IFetchRequestInfo,
} from './types';

const __REGISTRY = new WeakMap<IFetchInstance, IFetchInterceptor[]>();

function register(
  fetch: IFetchInstance,
  ...interceptors: IFetchInterceptor[]
): () => void {
  if (!__REGISTRY.has(fetch)) {
    __REGISTRY.set(fetch, []);
  }

  let __interceptors = __REGISTRY.get(fetch);
  __interceptors = __interceptors.concat(interceptors);
  __REGISTRY.set(fetch, __interceptors);

  return () => {
    let __interceptors = __REGISTRY.get(fetch);
    __interceptors = __interceptors.filter(
      (item) => !interceptors.includes(item)
    );
    __REGISTRY.set(fetch, __interceptors);
  };
}

function unregister(fetch: IFetchInstance) {
  __REGISTRY.delete(fetch);
}

function noop(Q: any) {
  return Q;
}

function execute(fetch: IFetchInstance, request: IFetchRequestInfo) {
  const interceptors = __REGISTRY.get(fetch) ?? [];
  let step = Promise.resolve<IFetchRequestInfo>(request);

  interceptors.forEach((interceptor) => {
    const { request = noop, requestError = noop } = interceptor;
    step = step.then(
      (req) => request.call(interceptor, req),
      (err) => requestError.call(interceptor, err)
    );
  });

  let step2 = step.then((req) => fetch(req.url, req));

  interceptors.forEach((interceptor) => {
    const { response = noop, responseError = noop } = interceptor;
    step2 = step2.then(
      (resp) => response.call(interceptor, resp),
      (err) => responseError.call(interceptor, err)
    );
  });

  return step2;
}

export function intercept(fetch: IFetchInstance): IFetchInstanceWrapper {
  function __fetch(input: RequestInfo, init?: RequestInit) {
    const req =
      typeof input === 'string' ? { url: input, ...init } : { ...input };
    return execute(fetch, req);
  }
  __fetch.use = (...interceptors: IFetchInterceptor[]) =>
    register(fetch, ...interceptors);
  __fetch.clear = () => unregister(fetch);
  return __fetch;
}

export default intercept;
